package com.example.rxohosaudio;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.annotations.NonNull;
import io.reactivex.annotations.Nullable;
import io.reactivex.functions.Consumer;
import ohos.global.resource.RawFileDescriptor;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.common.Source;
import ohos.media.player.Player;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

@SuppressWarnings({"unused", "WeakerAccess"})
public final class RxAudioPlayer {
    private static final String TAG = "RxAudioPlayer";
    private static HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG);
    private static final int RECORDER_TIME_UNIT = 1000;
    private Player mPlayer;

    private RxAudioPlayer() {
        // singleton
    }

    public static RxAudioPlayer getInstance() {
        return RxAudioPlayerHolder.INSTANCE;
    }

    private Player create(final PlayConfig config) throws IOException {
        stopPlay();

        Player player;
        switch (config.mType) {
            case PlayConfig.TYPE_URI:
                HiLog.warn(label, "MediaPlayer to start play uri: " + config.mUri);
                player = new Player(config.mContext);
                try {
//                    FileInputStream in = new FileInputStream(config.mUri.toString());
//                    FileDescriptor fd = in.getFD();
                    Source source = new Source(config.mUri.toString());
                    player.setSource(source);
                    return player;
                } catch (Exception e) {
                    HiLog.warn(new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG),
                            " IOException e" + e.getMessage());
                }

            case PlayConfig.TYPE_FILE:
                HiLog.warn(label, "MediaPlayer to start play file: " + config.mAudioFile.getName());
                player = new Player(config.mContext);
                try {
                    FileInputStream in = new FileInputStream(new File(config.mAudioFile.getAbsolutePath()));
                    FileDescriptor fd = in.getFD();
                    Source source = new Source(fd);
                    player.setSource(source);
                } catch (IOException e) {
                    HiLog.warn(label, " IOException e" + e.getMessage());
                }
                return player;
            case PlayConfig.TYPE_RES:
                HiLog.warn(label, "MediaPlayer to start play:" + config.mAudioResource);
                player = new Player(config.mContext);
                try {
                    RawFileDescriptor fd = config.mAudioResource.openRawFileDescriptor();
                    player.setSource(fd);
                } catch (IOException e) {
                    HiLog.warn(new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG),
                            " IOException e" + e.getMessage());
                }
                return player;
            case PlayConfig.TYPE_URL:
                HiLog.warn(label, "MediaPlayer to start play: " + config.mUrl);
                player = new Player(config.mContext);
                try {
                    Source source = new Source(config.mUrl);
                    player.setSource(source);
                } catch (Exception e) {
                    HiLog.warn(new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG),
                            " IOException e" + e.getMessage());
                }
                return player;
            default:
                // can't happen, just fix checkstyle
                throw new IllegalArgumentException("Unknown type: " + config.mType);
        }
    }

    /**
     * play audio from local file. should be scheduled in IO thread.
     */
    public Observable<Boolean> play(@Nullable final PlayConfig config) {
        if (!config.isArgumentValid()) {
            return Observable.error(new IllegalArgumentException(""));
        }

        return Observable.<Boolean>create(new ObservableOnSubscribe<Boolean>() {
            @Override
            public void subscribe(@io.reactivex.annotations.NonNull ObservableEmitter<Boolean> emitter) throws Exception {
                Player player = create(config);
                setMediaPlayerListener(player, emitter);
                //todo:Player not support left and right volume.
                player.setVolume(Math.max(config.mLeftVolume, config.mRightVolume));
                player.setAudioStreamType(config.mStreamType);
                player.enableSingleLooping(config.mLooping);
                if (config.needPrepare()) {
                    player.prepare();
                }
                player.play();
                mPlayer = player;
                emitter.onNext(true);
            }
        }).doOnError(new io.reactivex.functions.Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {
                stopPlay();
            }
        });
    }

    /**
     * prepare audio from local file. should be scheduled in IO thread.
     */
    public Observable<Boolean> prepare(@NonNull final PlayConfig config) {
        if (!config.isArgumentValid() || !config.isLocalSource()) {
            return Observable.error(new IllegalArgumentException(""));
        }

        return Observable.<Boolean>create(new ObservableOnSubscribe<Boolean>() {
            @Override
            public void subscribe(@io.reactivex.annotations.NonNull ObservableEmitter<Boolean> emitter) throws Exception {
                Player player = create(config);
                setMediaPlayerListener(player, emitter);
                //todo:Player not support left and right volume.
                player.setVolume(Math.max(config.mLeftVolume, config.mRightVolume));
                player.setAudioStreamType(config.mStreamType);
                player.enableSingleLooping(config.mLooping);
                if (config.needPrepare()) {
                    player.prepare();
                }
                mPlayer = player;
                emitter.onNext(true);
            }
        }).doOnError(new io.reactivex.functions.Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {
                stopPlay();
            }
        });
    }

    public void pause() {
        mPlayer.pause();
    }

    public void resume() {
        mPlayer.play();
    }

    /**
     * Non reactive API.
     */
    //TODO:@WorkerThread
    public boolean playNonRxy(@NonNull final PlayConfig config,
                              final Player.IPlayerCallback mplayerCallback) {
        if (!config.isArgumentValid()) {
            return false;
        }

        try {
            Player player = create(config);
            setMediaPlayerListener(player, mplayerCallback);
            //todo:Player not support left and right volume.
            player.setVolume(Math.max(config.mLeftVolume, config.mRightVolume));
            player.setAudioStreamType(config.mStreamType);
            player.enableSingleLooping(config.mLooping);
            if (config.needPrepare()) {
                player.prepare();
            }
            player.play();
            mPlayer = player;
            return true;
        } catch (RuntimeException | IOException e) {
            HiLog.warn(new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG),
                    "startPlay fail, IllegalArgumentException: " + e.getMessage());
            stopPlay();
            return false;
        }
    }

    public synchronized boolean stopPlay() {
        if (mPlayer == null) {
            return false;
        }
        mPlayer.setPlayerCallback(null);
        try {
            mPlayer.stop();
            mPlayer.reset();
            mPlayer.release();
        } catch (IllegalStateException e) {
            HiLog.warn(new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG),
                    "stopPlay fail, IllegalStateException: " + e.getMessage());
        }
        mPlayer = null;
        return true;
    }

    public int progress() {
        if (mPlayer != null) {
            return mPlayer.getCurrentTime() / 1000;
        }
        return 0;
    }

    /**
     * allow further customized manipulation.
     */
    public Player getMediaPlayer() {
        return mPlayer;
    }

    private void setMediaPlayerListener(final Player player,
                                        final ObservableEmitter<Boolean> emitter) {
        player.setPlayerCallback(new Player.IPlayerCallback() {
            @Override
            public void onPrepared() {

            }

            @Override
            public void onMessage(final int i, final int i1) {

            }

            @Override
            public void onResolutionChanged(final int i, final int i1) {

            }

            @Override
            public void onPlayBackComplete() {
                HiLog.warn(new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG),
                        "OnCompletionListener::onCompletion");
                // could not call stopPlay immediately, otherwise the second sound
                // could not play, thus no complete notification
                Observable.timer(50, TimeUnit.MILLISECONDS).subscribe(new io.reactivex.functions.Consumer<Long>() {
                    @Override
                    public void accept(final Long aLong) throws Exception {
                        stopPlay();
                        emitter.onComplete();
                    }
                }, new io.reactivex.functions.Consumer<Throwable>() {
                    @Override
                    public void accept(final Throwable throwable) throws Exception {
                        emitter.onError(throwable);
                    }
                });
            }

            @Override
            public void onError(final int what, final int extra) {
                HiLog.warn(new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG),
                        "OnErrorListener::onError" + what + extra);
                emitter.onError(new Throwable("Player error: " + what + ", " + extra));
                stopPlay();
            }

            @Override
            public void onRewindToComplete() {

            }

            @Override
            public void onBufferingChange(final int i) {

            }

            @Override
            public void onNewTimedMetaData(final Player.MediaTimedMetaData mediaTimedMetaData) {

            }

            @Override
            public void onMediaTimeIncontinuity(final Player.MediaTimeInfo mediaTimeInfo) {

            }
        });
    }

    private void setMediaPlayerListener(final Player player,
                                        final Player.IPlayerCallback playerCallback) {
        player.setPlayerCallback(new Player.IPlayerCallback() {
            @Override
            public void onPrepared() {

            }

            @Override
            public void onMessage(final int i, final int i1) {

            }

            @Override
            public void onError(final int i, final int i1) {
                HiLog.warn(new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG),
                        "OnErrorListener::onError" + i + i1);
                playerCallback.onError(i, i1);
                stopPlay();
            }

            @Override
            public void onResolutionChanged(final int i, final int i1) {

            }

            @Override
            public void onPlayBackComplete() {
                HiLog.warn(label, "onPlayBackCompleteListener::onPlayBackComplete");
                Observable.timer(50, TimeUnit.MILLISECONDS).subscribe(new io.reactivex.functions.Consumer<Long>() {
                    @Override
                    public void accept(final Long aLong) throws Exception {
                        stopPlay();
                        playerCallback.onPlayBackComplete();
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(final Throwable throwable) throws Exception {
                        HiLog.warn(label, "OnCompletionListener::onError,");
                    }
                });
            }

            @Override
            public void onRewindToComplete() {

            }

            @Override
            public void onBufferingChange(final int i) {

            }

            @Override
            public void onNewTimedMetaData(final Player.MediaTimedMetaData mediaTimedMetaData) {

            }

            @Override
            public void onMediaTimeIncontinuity(final Player.MediaTimeInfo mediaTimeInfo) {

            }
        });
    }

    private static class RxAudioPlayerHolder {
        private static final RxAudioPlayer INSTANCE = new RxAudioPlayer();
    }

}
